/*
 * MIT License
 * 
 * Copyright (c) 2017 wen.gu <454727014@qq.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: vos_vfs_win.c
 *
 * Purpose: windows file operation interface
 *
 * Developer:
 *   wen.gu , 2015-10-23
 *
 * TODO: 
 *
 ***************************************************************************/


//inner header file
#include "sys_config.h"


#if defined(USE_WINDOWS_FILE_INTERFACE) && USE_WINDOWS_FILE_INTERFACE
//API header file
#include "vos_vfs.h"
#include "vos_mutex.h"
#include "vos_mem.h"
#include "general_error.h"
#include "general_macro.h"

#include <windows.h>
#include <tchar.h>
#include <stdlib.h>
#include <stdio.h>

#define LOGE printf
/***************************************************************************
*
* data struct define
*
***************************************************************************/
typedef struct winDirHandle_s
{
    HANDLE dirHd;
    WIN32_FIND_DATA findData;
}winDirHandle_t;

/***************************************************************************
*
* inner function define
*
***************************************************************************/
//return dwDesiredAccess
static GU32 getFileOpenMode(vfs_open_mode_t mode, GU32* dwCreationDisposition)
{
    GU32 opMode = 0;
    switch (mode)
    {
    case VFS_MODE_R:   
    {
        opMode = GENERIC_READ; 
        *dwCreationDisposition = OPEN_EXISTING;
    }
        break;
    case VFS_MODE_RW: 
    {
        opMode = GENERIC_READ | GENERIC_WRITE;
        *dwCreationDisposition = OPEN_ALWAYS;
    }
        break;
    case VFS_MODE_WCA: 
    {
        opMode = FILE_APPEND_DATA;
        *dwCreationDisposition = OPEN_ALWAYS;
    }
    break;
    case VFS_MODE_WCT:
    {
        opMode = GENERIC_WRITE;
        *dwCreationDisposition = CREATE_ALWAYS;
    }
    break;
    case VFS_MODE_RWCA: 
    {
        opMode = FILE_APPEND_DATA | FILE_READ_DATA;
        *dwCreationDisposition = OPEN_ALWAYS;
    }
    break;
    case VFS_MODE_RWCT: 
    {
        opMode = GENERIC_WRITE | GENERIC_READ;
        *dwCreationDisposition = CREATE_ALWAYS;
    }
    break;
    default:
        break;
    }

    return opMode;
}
/***************************************************************************
*
* API define
*
***************************************************************************/

/***************************************************************************
* directory API implement
***************************************************************************/
GPHD vfsOpenDir(const char* dirPath)
{
    GCHECK(dirPath);
    const char suffixName[] = "*.dll";
    GU32 suffixLen = strlen(suffixName);
    GU32 pathLen = strlen(dirPath);
    GU32 likeSize = pathLen + suffixLen;
    char* likeName = memAlloc(likeSize + 1);

    if (!likeName)
    {
        LOGE("alloc memory for dir path name failed\n");
        return NULL;
    }

    memcpy(likeName, dirPath, pathLen);
    memcpy(likeName + pathLen, suffixName, suffixLen);
    likeName[likeSize] = '\0';

#if defined(UNICODE)
    GU32 allocSize = likeSize * 2 + 2;
    WCHAR* path = memAlloc(allocSize);

    if (!path)
    {
        memFree(likeName);
        LOGE("alloc wchar dir path name failed\n");
        return NULL;
    }

    if (mbstowcs(path, likeName, allocSize) == -1)
    {
        memFree(likeName);
        memFree(path);
        return NULL;
    }
    memFree(likeName);
    
#else
    char* path = likeName;
#endif
    winDirHandle_t* dir = memAlloc(sizeof(winDirHandle_t));

    if (!dir)
    {
        memFree(path);
        LOGE("Alloc dir handle failed, path(%s)\n", dirPath);
        return NULL;
    }

    HANDLE hd = FindFirstFile(path, &(dir->findData));

    memFree(path);
    
    if (INVALID_HANDLE_VALUE == hd)
    {
        memFree(dir);
        LOGE("open dir (%s) failed(%d)\n", dirPath, GetLastError());
        return NULL;
    }

    dir->dirHd = hd;

    return dir;
}

void vfsCloseDir(GPHD dirHd)
{
    if (dirHd)
    {
        winDirHandle_t* dir = (winDirHandle_t*)dirHd;
        FindClose(dir->dirHd);
        memFree(dir);
    }
}

const char* vfsReadFileInDir(GPHD dirHd)
{
    const char* fileName = NULL;
    if (dirHd)
    {
        winDirHandle_t* dir = (winDirHandle_t*)dirHd;

        BOOL ret = FindNextFile(dir->dirHd, &(dir->findData));

        if (FALSE == ret)
        {
            DWORD dwErr = GetLastError();

            if (dwErr != ERROR_NO_MORE_FILES)
            {
                LOGE("find next file failed(%d)\n", dwErr);
            }
        }
        else if (dir->findData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
        {//if a directory,then skip if and find next file.
            fileName = vfsReadFileInDir(dirHd);
        }
        else
        {
            fileName = dir->findData.cFileName;
        }        
    }

    if (fileName)
    {
#if defined(UNICODE)
#error "todo implement this case"
#endif
    }

    return fileName;
}

/***************************************************************************
* file API implement
***************************************************************************/
GPHD vfsFileOpen(const char* name, vfs_open_mode_t mode)
{
    HANDLE* fp = NULL;
    if (name)
    {   
        GU32 dwCreationDisposition = 0;
        GU32 opMode = getFileOpenMode(mode, &dwCreationDisposition);

        if (opMode)
        {
            fp = CreateFile(name, opMode, 0, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);

            if (fp == INVALID_HANDLE_VALUE)
            {
                printf("create file(%s) failed(%d)\n", name, GetLastError());
                fp = NULL;
            }
        }
    }    

    return (GPHD)fp;
}

void vfsFileClose(GPHD fhd)
{
    if (fhd)
    {
        CloseHandle(fhd);
    }
}

GS32 vfsFileRead(GPHD fhd, void* buf, GU32 size)
{
    if (fhd)
    {
        GU32 readSize = 0;
        BOOL readRet = ReadFile(fhd,buf, size, (LPDWORD)&readSize, NULL);

        if (readRet == TRUE)
        {
            return readSize;
        }
        else
        {
            //todo get error.
        }
    }

    return G_FAIL;    
}

GS32 vfsFileWrite(GPHD fhd, const void* buf, GU32 size)
{
    if (fhd)
    {
        GU32 writeSize = 0;
        BOOL writeRet = WriteFile(fhd, buf, size, (LPDWORD)&writeSize, NULL);

        if (writeRet == TRUE)
        {
            return writeSize;
        }
        else
        {
            //todo get error.
        }
    }

    return G_FAIL;
}

GS64 vfsFileSeek(GPHD fhd, GS64 offset, vfs_seek_mode_t whence)
{
    if (fhd)
    {
        GU32 moveMethoed = 0; //default method
        LARGE_INTEGER myOffset = { 0 };
        myOffset.QuadPart = offset;

        switch (whence)
        {
        case  VFS_SEEK_SET: moveMethoed = FILE_BEGIN; break;
        case  VFS_SEEK_CUR: moveMethoed = FILE_CURRENT; break;
        case  VFS_SEEK_END: moveMethoed = FILE_END; break;
        default:
            //todo log error.
            goto EXIT;
            break;
        }

        myOffset.LowPart = SetFilePointer(fhd, myOffset.LowPart, &(myOffset.HighPart), moveMethoed);

        if (myOffset.LowPart != INVALID_SET_FILE_POINTER)
        {
            return (GS64)myOffset.QuadPart;
        }
    }

EXIT:
    return G_FAIL;
}

GS64 vfsFileTell(GPHD fhd)
{
    if (fhd)
    {
        LARGE_INTEGER myOffset = { 0 };
        
        myOffset.LowPart = SetFilePointer(fhd, 0, &myOffset.HighPart, FILE_CURRENT);

        if (myOffset.LowPart != INVALID_SET_FILE_POINTER)
        {
            return (GS64)myOffset.QuadPart;
        }
    }
    
    return G_FAIL;
}

GS32 vfsFileSync(GPHD fhd)
{
    if (fhd)
    {
        if (FlushFileBuffers(fhd) == TRUE)
        {
            return G_OK;
        }
    }

    return G_FAIL;
}

GS64 vfsFileSize(GPHD fhd)
{
    if (fhd)
    {
        LARGE_INTEGER size = {0};
        size.LowPart = GetFileSize(fhd, &(size.HighPart));

        if (size.LowPart != INVALID_FILE_SIZE)
        {
            return size.QuadPart;
        }        
    }

    return 0;
}

#endif
